package com.zon.len.dynamic.properties;

import static com.alibaba.druid.pool.DruidAbstractDataSource.DEFAULT_INITIAL_SIZE;
import static com.alibaba.druid.pool.DruidAbstractDataSource.DEFAULT_MAX_EVICTABLE_IDLE_TIME_MILLIS;
import static com.alibaba.druid.pool.DruidAbstractDataSource.DEFAULT_MAX_WAIT;
import static com.alibaba.druid.pool.DruidAbstractDataSource.DEFAULT_MIN_EVICTABLE_IDLE_TIME_MILLIS;
import static com.alibaba.druid.pool.DruidAbstractDataSource.DEFAULT_MIN_IDLE;
import static com.alibaba.druid.pool.DruidAbstractDataSource.DEFAULT_PHY_TIMEOUT_MILLIS;
import static com.alibaba.druid.pool.DruidAbstractDataSource.DEFAULT_TEST_ON_BORROW;
import static com.alibaba.druid.pool.DruidAbstractDataSource.DEFAULT_TIME_BETWEEN_EVICTION_RUNS_MILLIS;
import static com.alibaba.druid.pool.DruidAbstractDataSource.DEFAULT_WHILE_IDLE;

import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.context.properties.NestedConfigurationProperty;

/**
 * @author ZonLen since on 2022/1/1 上午9:42
 */
@Getter
@Setter
@NoArgsConstructor
@Slf4j
public class DruidConfigProperties {

  private static final String INITIAL_SIZE = "druid.initialSize";
  private static final String MAX_ACTIVE = "druid.maxActive";
  private static final String MIN_IDLE = "druid.minIdle";
  private static final String MAX_WAIT = "druid.maxWait";

  private static final String TIME_BETWEEN_EVICTION_RUNS_MILLIS = "druid.timeBetweenEvictionRunsMillis";
  private static final String TIME_BETWEEN_LOG_STATS_MILLIS = "druid.timeBetweenLogStatsMillis";
  private static final String MIN_EVICTABLE_IDLE_TIME_MILLIS = "druid.minEvictableIdleTimeMillis";
  private static final String MAX_EVICTABLE_IDLE_TIME_MILLIS = "druid.maxEvictableIdleTimeMillis";

  private static final String TEST_WHILE_IDLE = "druid.testWhileIdle";
  private static final String TEST_ON_BORROW = "druid.testOnBorrow";
  private static final String VALIDATION_QUERY = "druid.validationQuery";
  private static final String USE_GLOBAL_DATA_SOURCE_STAT = "druid.useGlobalDataSourceStat";
  private static final String ASYNC_INIT = "druid.asyncInit";

  private static final String FILTERS = "druid.filters";
  private static final String CLEAR_FILTERS_ENABLE = "druid.clearFiltersEnable";
  private static final String RESET_STAT_ENABLE = "druid.resetStatEnable";
  private static final String NOT_FULL_TIMEOUT_RETRY_COUNT = "druid.notFullTimeoutRetryCount";
  private static final String MAX_WAIT_THREAD_COUNT = "druid.maxWaitThreadCount";

  private static final String FAIL_FAST = "druid.failFast";
  private static final String PHY_TIMEOUT_MILLIS = "druid.phyTimeoutMillis";
  private static final String KEEP_ALIVE = "druid.keepAlive";
  private static final String POOL_PREPARED_STATEMENTS = "druid.poolPreparedStatements";
  private static final String INIT_VARIANTS = "druid.initVariants";
  private static final String INIT_GLOBAL_VARIANTS = "druid.initGlobalVariants";
  private static final String USE_UNFAIR_LOCK = "druid.useUnfairLock";
  private static final String KILL_WHEN_SOCKET_READ_TIMEOUT = "druid.killWhenSocketReadTimeout";
  private static final String MAX_POOL_PREPARED_STATEMENT_PER_CONNECTION_SIZE = "druid.maxPoolPreparedStatementPerConnectionSize";
  private static final String INIT_CONNECTION_SQLS = "druid.initConnectionSqls";

  private static final String STAT_SQL_MAX_SIZE = "druid.stat.sql.MaxSize";
  private static final String STAT_LOG_SLOW_SQL = "druid.stat.logSlowSql";
  private static final String STAT_SLOW_SQL_MILLIS = "druid.stat.slowSqlMillis";
  private static final String STAT_MERGE_SQL = "druid.stat.mergeSql";

  private String username;
  private String password;
  private String url;
  private String driverClassName = "com.mysql.cj.jdbc.Driver";
  private String sourceKey;

  private Integer initialSize;
  private Integer maxActive;
  private Integer minIdle;
  private Integer maxWait;
  private Long timeBetweenEvictionRunsMillis;
  private Long timeBetweenLogStatsMillis;
  private Integer statSqlMaxSize;
  private Long minEvictableIdleTimeMillis;
  private Long maxEvictableIdleTimeMillis;
  private Boolean testWhileIdle;
  private Boolean testOnBorrow;
  private Boolean testOnReturn;
  private String validationQuery;
  private Integer validationQueryTimeout;
  private Boolean useGlobalDataSourceStat;
  private Boolean asyncInit;
  private String filters;
  private Boolean clearFiltersEnable;
  private Boolean resetStatEnable;
  private Integer notFullTimeoutRetryCount;
  private Integer maxWaitThreadCount;
  private Boolean failFast;
  private Long phyTimeoutMillis;
  private Boolean keepAlive;
  private Boolean poolPreparedStatements;
  private Boolean initVariants;
  private Boolean initGlobalVariants;
  private Boolean useUnfairLock;
  private Boolean killWhenSocketReadTimeout;
  private Properties connectionProperties;
  private Integer maxPoolPreparedStatementPerConnectionSize;
  private String initConnectionSqls;
  private Boolean sharePreparedStatements;
  private Integer connectionErrorRetryAttempts;
  private Boolean breakAfterAcquireFailure;
  private Boolean removeAbandoned;
  private Integer removeAbandonedTimeoutMillis;
  private Boolean logAbandoned;
  private Integer queryTimeout;
  private Integer transactionQueryTimeout;
  private String publicKey;

  @NestedConfigurationProperty
  private DruidWallConfig wall = new DruidWallConfig();

  @NestedConfigurationProperty
  private DruidStatConfig stat = new DruidStatConfig();

  @NestedConfigurationProperty
  private DruidSlf4jConfig slf4j = new DruidSlf4jConfig();

  private List<String> proxyFilters = new ArrayList<>();

  /**
   * Druid日志配置
   *
   * @author ZL since on 2022/1/1 上午9:49
   */
  @Getter
  @Setter
  public static class DruidSlf4jConfig {

    private Boolean enable = true;

    private Boolean statementExecutableSqlLogEnable = false;
  }

  /**
   * Druid监控配置
   *
   * @author ZL since on 2022/1/1 上午9:50
   */
  @Getter
  @Setter
  public static class DruidStatConfig {

    private Long slowSqlMillis;

    private Boolean logSlowSql;

    private Boolean mergeSql;
  }

  /**
   * Druid防火墙
   *
   * @author ZL since on 2022/1/1 上午9:50
   */
  @Getter
  @Setter
  public static class DruidWallConfig {

    private Boolean noneBaseStatementAllow;
    private Boolean callAllow;
    private Boolean selectAllow;
    private Boolean selectIntoAllow;
    private Boolean selectIntoOutfileAllow;
    private Boolean selectWhereAlwayTrueCheck;
    private Boolean selectHavingAlwayTrueCheck;
    private Boolean selectUnionCheck;
    private Boolean selectMinusCheck;
    private Boolean selectExceptCheck;
    private Boolean selectIntersectCheck;
    private Boolean createTableAllow;
    private Boolean dropTableAllow;
    private Boolean alterTableAllow;
    private Boolean renameTableAllow;
    private Boolean hintAllow;
    private Boolean lockTableAllow;
    private Boolean startTransactionAllow;
    private Boolean blockAllow;

    private Boolean conditionAndAlwayTrueAllow;
    private Boolean conditionAndAlwayFalseAllow;
    private Boolean conditionDoubleConstAllow;
    private Boolean conditionLikeTrueAllow;

    private Boolean selectAllColumnAllow;

    private Boolean deleteAllow;
    private Boolean deleteWhereAlwayTrueCheck;
    private Boolean deleteWhereNoneCheck;

    private Boolean updateAllow;
    private Boolean updateWhereAlayTrueCheck;
    private Boolean updateWhereNoneCheck;

    private Boolean insertAllow;
    private Boolean mergeAllow;
    private Boolean minusAllow;
    private Boolean intersectAllow;
    private Boolean replaceAllow;
    private Boolean setAllow;
    private Boolean commitAllow;
    private Boolean rollbackAllow;
    private Boolean useAllow;

    private Boolean multiStatementAllow;

    private Boolean truncateAllow;

    private Boolean commentAllow;
    private Boolean strictSyntaxCheck;
    private Boolean constArithmeticAllow;
    private Boolean limitZeroAllow;

    private Boolean describeAllow;
    private Boolean showAllow;

    private Boolean schemaCheck;
    private Boolean tableCheck;
    private Boolean functionCheck;
    private Boolean objectCheck;
    private Boolean variantCheck;

    private Boolean mustParameterized;

    private Boolean doPrivilegedAllow;

    private String dir;

    private String tenantTablePattern;
    private String tenantColumn;

    private Boolean wrapAllow;
    private Boolean metadataAllow;

    private Boolean conditionOpXorAllow;
    private Boolean conditionOpBitwseAllow;

    private Boolean caseConditionConstAllow;

    private Boolean completeInsertValuesCheck;
    private Integer insertValuesCheckSize;

    private Integer selectLimit;
  }

  private static final String TRUE = "true";
  private static final String FALSE = "false";

  /**
   * 根据全局配置和本地配置结合转换为Properties
   *
   * @param g 全局配置
   * @return Druid配置
   */
  public Properties toProperties(DruidConfigProperties g) {
    Properties properties = new Properties();
    Integer initialSize = this.initialSize == null ? g.getInitialSize() : this.initialSize;
    if (initialSize != null && !initialSize.equals(DEFAULT_INITIAL_SIZE)) {
      properties.setProperty(INITIAL_SIZE, String.valueOf(initialSize));
    }

    Integer maxActive = this.maxActive == null ? g.getMaxActive() : this.maxActive;
    if (maxActive != null && !maxActive.equals(DEFAULT_MAX_WAIT)) {
      properties.setProperty(MAX_ACTIVE, String.valueOf(maxActive));
    }

    Integer minIdle = this.minIdle == null ? g.getMinIdle() : this.minIdle;
    if (minIdle != null && !minIdle.equals(DEFAULT_MIN_IDLE)) {
      properties.setProperty(MIN_IDLE, String.valueOf(minIdle));
    }

    Integer maxWait = this.maxWait == null ? g.getMaxWait() : this.maxWait;
    if (maxWait != null && !maxWait.equals(DEFAULT_MAX_WAIT)) {
      properties.setProperty(MAX_WAIT, String.valueOf(maxWait));
    }

    Long timeBetweenEvictionRunsMillis =
        this.timeBetweenEvictionRunsMillis == null ? g.getTimeBetweenEvictionRunsMillis()
            : this.timeBetweenEvictionRunsMillis;
    if (timeBetweenEvictionRunsMillis != null && !timeBetweenEvictionRunsMillis
        .equals(DEFAULT_TIME_BETWEEN_EVICTION_RUNS_MILLIS)) {
      properties.setProperty(TIME_BETWEEN_EVICTION_RUNS_MILLIS,
          String.valueOf(timeBetweenEvictionRunsMillis));
    }

    Long timeBetweenLogStatsMillis =
        this.timeBetweenLogStatsMillis == null ? g.getTimeBetweenLogStatsMillis()
            : this.timeBetweenLogStatsMillis;
    if (timeBetweenLogStatsMillis != null && timeBetweenLogStatsMillis > 0) {
      properties
          .setProperty(TIME_BETWEEN_LOG_STATS_MILLIS, String.valueOf(timeBetweenLogStatsMillis));
    }

    Long minEvictableIdleTimeMillis =
        this.minEvictableIdleTimeMillis == null ? g.getMinEvictableIdleTimeMillis()
            : this.minEvictableIdleTimeMillis;
    if (minEvictableIdleTimeMillis != null && !minEvictableIdleTimeMillis
        .equals(DEFAULT_MIN_EVICTABLE_IDLE_TIME_MILLIS)) {
      properties
          .setProperty(MIN_EVICTABLE_IDLE_TIME_MILLIS, String.valueOf(minEvictableIdleTimeMillis));
    }

    Long maxEvictableIdleTimeMillis =
        this.maxEvictableIdleTimeMillis == null ? g.getMaxEvictableIdleTimeMillis()
            : this.maxEvictableIdleTimeMillis;
    if (maxEvictableIdleTimeMillis != null && !maxEvictableIdleTimeMillis
        .equals(DEFAULT_MAX_EVICTABLE_IDLE_TIME_MILLIS)) {
      properties
          .setProperty(MAX_EVICTABLE_IDLE_TIME_MILLIS, String.valueOf(maxEvictableIdleTimeMillis));
    }

    Boolean testWhileIdle = this.testWhileIdle == null ? g.getTestWhileIdle() : this.testWhileIdle;
    if (testWhileIdle != null && !testWhileIdle.equals(DEFAULT_WHILE_IDLE)) {
      properties.setProperty(TEST_WHILE_IDLE, FALSE);
    }

    Boolean testOnBorrow = this.testOnBorrow == null ? g.getTestOnBorrow() : this.testOnBorrow;
    if (testOnBorrow != null && !testOnBorrow.equals(DEFAULT_TEST_ON_BORROW)) {
      properties.setProperty(TEST_ON_BORROW, TRUE);
    }

    String validationQuery =
        this.validationQuery == null ? g.getValidationQuery() : this.validationQuery;
    if (validationQuery != null && validationQuery.length() > 0) {
      properties.setProperty(VALIDATION_QUERY, validationQuery);
    }

    Boolean useGlobalDataSourceStat =
        this.useGlobalDataSourceStat == null ? g.getUseGlobalDataSourceStat()
            : this.useGlobalDataSourceStat;
    if (useGlobalDataSourceStat != null && useGlobalDataSourceStat.equals(Boolean.TRUE)) {
      properties.setProperty(USE_GLOBAL_DATA_SOURCE_STAT, TRUE);
    }

    Boolean asyncInit = this.asyncInit == null ? g.getAsyncInit() : this.asyncInit;
    if (asyncInit != null && asyncInit.equals(Boolean.TRUE)) {
      properties.setProperty(ASYNC_INIT, TRUE);
    }

    //filters单独处理，默认了stat,wall
    String filters = this.filters == null ? g.getFilters() : this.filters;
    if (filters == null) {
      filters = "stat,wall";
    }
    if (publicKey != null && publicKey.length() > 0 && !filters.contains("config")) {
      filters += ",config";
    }
    properties.setProperty(FILTERS, filters);

    Boolean clearFiltersEnable =
        this.clearFiltersEnable == null ? g.getClearFiltersEnable() : this.clearFiltersEnable;
    if (clearFiltersEnable != null && clearFiltersEnable.equals(Boolean.FALSE)) {
      properties.setProperty(CLEAR_FILTERS_ENABLE, FALSE);
    }

    Boolean resetStatEnable =
        this.resetStatEnable == null ? g.getResetStatEnable() : this.resetStatEnable;
    if (resetStatEnable != null && resetStatEnable.equals(Boolean.FALSE)) {
      properties.setProperty(RESET_STAT_ENABLE, FALSE);
    }

    Integer notFullTimeoutRetryCount =
        this.notFullTimeoutRetryCount == null ? g.getNotFullTimeoutRetryCount()
            : this.notFullTimeoutRetryCount;
    if (notFullTimeoutRetryCount != null && !notFullTimeoutRetryCount.equals(0)) {
      properties
          .setProperty(NOT_FULL_TIMEOUT_RETRY_COUNT, String.valueOf(notFullTimeoutRetryCount));
    }

    Integer maxWaitThreadCount =
        this.maxWaitThreadCount == null ? g.getMaxWaitThreadCount() : this.maxWaitThreadCount;
    if (maxWaitThreadCount != null && !maxWaitThreadCount.equals(-1)) {
      properties.setProperty(MAX_WAIT_THREAD_COUNT, String.valueOf(maxWaitThreadCount));
    }

    Boolean failFast = this.failFast == null ? g.getFailFast() : this.failFast;
    if (failFast != null && failFast.equals(Boolean.TRUE)) {
      properties.setProperty(FAIL_FAST, TRUE);
    }

    Long phyTimeoutMillis =
        this.phyTimeoutMillis == null ? g.getPhyTimeoutMillis() : this.phyTimeoutMillis;
    if (phyTimeoutMillis != null && !phyTimeoutMillis.equals(DEFAULT_PHY_TIMEOUT_MILLIS)) {
      properties.setProperty(PHY_TIMEOUT_MILLIS, String.valueOf(phyTimeoutMillis));
    }

    Boolean keepAlive = this.keepAlive == null ? g.getKeepAlive() : this.keepAlive;
    if (keepAlive != null && keepAlive.equals(Boolean.TRUE)) {
      properties.setProperty(KEEP_ALIVE, TRUE);
    }

    Boolean poolPreparedStatements =
        this.poolPreparedStatements == null ? g.getPoolPreparedStatements()
            : this.poolPreparedStatements;
    if (poolPreparedStatements != null && poolPreparedStatements.equals(Boolean.TRUE)) {
      properties.setProperty(POOL_PREPARED_STATEMENTS, TRUE);
    }

    Boolean initVariants = this.initVariants == null ? g.getInitVariants() : this.initVariants;
    if (initVariants != null && initVariants.equals(Boolean.TRUE)) {
      properties.setProperty(INIT_VARIANTS, TRUE);
    }

    Boolean initGlobalVariants =
        this.initGlobalVariants == null ? g.getInitGlobalVariants() : this.initGlobalVariants;
    if (initGlobalVariants != null && initGlobalVariants.equals(Boolean.TRUE)) {
      properties.setProperty(INIT_GLOBAL_VARIANTS, TRUE);
    }

    Boolean useUnfairLock = this.useUnfairLock == null ? g.getUseUnfairLock() : this.useUnfairLock;
    if (useUnfairLock != null) {
      properties.setProperty(USE_UNFAIR_LOCK, String.valueOf(useUnfairLock));
    }

    Boolean killWhenSocketReadTimeout =
        this.killWhenSocketReadTimeout == null ? g.getKillWhenSocketReadTimeout()
            : this.killWhenSocketReadTimeout;
    if (killWhenSocketReadTimeout != null && killWhenSocketReadTimeout.equals(Boolean.TRUE)) {
      properties.setProperty(KILL_WHEN_SOCKET_READ_TIMEOUT, TRUE);
    }

    Properties connectProperties =
        connectionProperties == null ? g.getConnectionProperties() : connectionProperties;

    if (publicKey != null && publicKey.length() > 0) {
      if (connectProperties == null) {
        connectProperties = new Properties();
      }
      connectProperties.setProperty("config.decrypt", TRUE);
      connectProperties.setProperty("config.decrypt.key", publicKey);
    }
    this.connectionProperties = connectProperties;

    Integer maxPoolPreparedStatementPerConnectionSize =
        this.maxPoolPreparedStatementPerConnectionSize == null ? g
            .getMaxPoolPreparedStatementPerConnectionSize()
            : this.maxPoolPreparedStatementPerConnectionSize;
    if (maxPoolPreparedStatementPerConnectionSize != null
        && !maxPoolPreparedStatementPerConnectionSize.equals(10)) {
      properties.setProperty(MAX_POOL_PREPARED_STATEMENT_PER_CONNECTION_SIZE,
          String.valueOf(maxPoolPreparedStatementPerConnectionSize));
    }

    String initConnectionSqls =
        this.initConnectionSqls == null ? g.getInitConnectionSqls() : this.initConnectionSqls;
    if (initConnectionSqls != null && initConnectionSqls.length() > 0) {
      properties.setProperty(INIT_CONNECTION_SQLS, initConnectionSqls);
    }

    //stat配置参数
    Integer statSqlMaxSize =
        this.statSqlMaxSize == null ? g.getStatSqlMaxSize() : this.statSqlMaxSize;
    if (statSqlMaxSize != null) {
      properties.setProperty(STAT_SQL_MAX_SIZE, String.valueOf(statSqlMaxSize));
    }

    Boolean logSlowSql =
        stat.getLogSlowSql() == null ? g.stat.getLogSlowSql() : stat.getLogSlowSql();
    if (logSlowSql != null && logSlowSql) {
      properties.setProperty(STAT_LOG_SLOW_SQL, TRUE);
    }
    Long slowSqlMillis =
        stat.getSlowSqlMillis() == null ? g.stat.getSlowSqlMillis() : stat.getSlowSqlMillis();
    if (slowSqlMillis != null) {
      properties.setProperty(STAT_SLOW_SQL_MILLIS, slowSqlMillis.toString());
    }

    Boolean mergeSql = stat.getMergeSql() == null ? g.stat.getMergeSql() : stat.getMergeSql();
    if (mergeSql != null && mergeSql) {
      properties.setProperty(STAT_MERGE_SQL, TRUE);
    }
    return properties;
  }

}